package org.afox.parsers.xml;

import java.util.*;
import java.lang.reflect.*;

import org.xml.sax.*;
import org.xml.sax.helpers.*;

/** @version 1.5 */

public abstract class ReflectiveHandler extends DefaultHandler
{
	private BaseHandler mainHandler;
	private Hashtable tagMethods;
	private Class[] classParms = new Class[1];
	private String handlerName;
	private LinkedList characterMethods = new LinkedList();


	public ReflectiveHandler()
	{
		setupSetMethods();
	}

	public void setMainHandler(BaseHandler aHandler)
	{
		if (aHandler==null)
			throw new IllegalArgumentException("ReflectiveHandler:setMainHandler: handler cannot be null");
		mainHandler = aHandler;
	}

	public void setName(String aName)
	{
		handlerName = aName;
	}

	public BaseHandler getMainHandler()
	{
		return mainHandler;
	}

	public void popHandler()
	{
		mainHandler.popHandler();
	}

	/*
	  Note: I discovered a bug in this method when I migrated functionality into this class.  It was possible that
	  a handler would attempt to push another instance of itself onto the stack which would result in an endless loop
	  and eventual stack overflow.  To deal with this problem, I added the check to ensure that the name of
	  the new handler would not resolve to the same name as the current handler.
	*/
	public ReflectiveHandler pushNewHandler(String name) throws SAXException
	{
		if (name.equals(handlerName))
			throw new RuntimeException("Cannot push equivalent instance");
		return mainHandler.pushNewHandler(name);
	}

	public ReflectiveHandler getParentHandler() throws SAXException
	{
		return mainHandler.getParentHandler(this);
	}

	private void setupSetMethods()
	{
		tagMethods = new Hashtable();	

		try
		{
			classParms[0] = Class.forName("org.xml.sax.Attributes");
		}
		catch (Exception x)
		{
			System.out.println("CommandHandler():" + x);
		}
		parseMethods(getClass().getMethods());
	}

	private void parseMethods(Method[] methods)
	{
		for (int i = 0; i<methods.length; i++)
		{
			Class[] parms = methods[i].getParameterTypes();
			if (methods[i].getName().startsWith("start"))
			{
				if ( (parms.length == classParms.length) &&
				     (parms[0].equals(classParms[0])))
				{
					tagMethods.put(methods[i].getName(), methods[i]);
				}
			}
			else if (methods[i].getName().startsWith("end"))
			{
				if (parms.length == 0)
				{
					tagMethods.put(methods[i].getName(), methods[i]);
				}
			}
		}
	}

	public void startElement(String namespace, String localName, String qualifiedName, Attributes attrs) 
                throws SAXException
        {
		pushCharacterMethod("character" + qualifiedName);
		try
		{
			Method aMethod = (Method) tagMethods.get("start" + qualifiedName);

			Object[] parms = new Object[1];
			parms[0] = attrs;

			aMethod.invoke(this, parms);
		}
		catch(Exception x)
		{
			try
			{
				pushNewHandler(qualifiedName).startElement(namespace, localName, qualifiedName, attrs);
			}
			catch(Exception y)
			{
				startElementImpl(namespace, localName, qualifiedName, attrs);
			}
		}
	}

	public void endElement(String namespace, String localName, String qualifiedName) 
		throws SAXException
	{
		popCharacterMethod();
		try
		{
			Method aMethod = (Method) tagMethods.get("end" + qualifiedName);

			Object[] parms = new Object[0];

			aMethod.invoke(this, parms);
		}
		catch(Exception x)
		{
			endElementImpl(namespace, localName, qualifiedName);
		}
	}

	public void characters(char[] ch, int start, int end) throws SAXException
        {
		invokeCharacterMethod(new String(ch, start, end));
        }

	public void startElementImpl(String namespace, String localName, String qualifiedName, Attributes attrs) 
                throws SAXException
	{
		// Override this method if you wish to provide functionality for the SAX startElement Method
	}

	public void endElementImpl(String namespace, String localName, String qualifiedName) 
		throws SAXException
	{
		// Override this method if you wish to provide functionality for the SAX endElement Method
	}

	public void pushCharacterMethod(String name)
	{
		try
		{
			Class[] parms = {Class.forName("java.lang.String")};
			Method characterMethod = getClass().getMethod(name, parms);
			characterMethods.addLast(characterMethod);
		}
		catch (Exception x)
		{
			characterMethods.addLast(NullMethod.nullMethod);
		}
	}

	public void popCharacterMethod()
	{
		characterMethods.removeLast();
	}

	/** This method is no longer used */
	public void clearCharacterMethod()
	{
		//		characterMethod = null;
	}

	public void invokeCharacterMethod(String value)
	{
		if (characterMethods.getLast() == NullMethod.nullMethod)
			return;
		try
		{
			Method characterMethod = (Method) characterMethods.getLast();
			Object[] parms = {value};
			characterMethod.invoke(this, parms);
		}
		catch (Exception x)
		{
			System.out.println("Error invoking character delegate method:" + x);
		}
	}

}

class NullMethod
{
	public static NullMethod nullMethod = new NullMethod();
}
